package net.andreinc.jet.xml;

import java.io.File;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

/**
 * Element class .
 * 
 * @author Andrei Ciobanu
 */

public class XMLElement {
	private String tag = null;
	private String text = null;
	private Map<String, String> attributes = null;
	private List<XMLElement> directChildren = null;
	private XMLElement parent = null;

	/**
	 * Resets the current element . Removes all sub-elements and attributes and
	 * sets text and tail attribute to null .
	 */
	public void clear() {
		this.attributes.clear();
		this.directChildren.clear();
		this.text = "";
		this.tag = "";
	}

	/**
	 * Retrieves the attribute named key .
	 * 
	 * @param attributeName
	 * @return The attribute named key or null (if the attribute is not found) .
	 */
	public String getAttribute(String attributeName) {
		return this.attributes.get(attributeName);
	}

	/**
	 * Set the attribute key to value . If the key is present the value is
	 * updated, if the key is not present the attribute is added .
	 * 
	 * @param attributeName
	 * @param attributeValue
	 */
	public void setAttribute(String attributeName, String attributeValue) {
		this.attributes.put(attributeName, attributeValue);
	}

	/**
	 * Test if a the element has the specified attribute .
	 * 
	 * @param attributeName
	 * @return
	 */
	public boolean hasAttribute(String attributeName) {
		return this.attributes.containsKey(attributeName);
	}

	/**
	 * Remove an attribute if exists .
	 * 
	 * @param attributeName
	 *            The attribute name to be removed .
	 */
	public void removeAttribute(String attributeName) {
		this.attributes.remove(attributeName);
	}
	
	/**
	 * Returns the elemenent's attributes as a MAP .
	 * @return 
	 */
	public Map<String, String> getAttributes() {
		return attributes;
	}

	/**
	 * Replace the current elements attributes with your own .
	 * @param attributes
	 */
	public void setAttributes(Map<String, String> attributes) {
		this.attributes = attributes;
	}

	/**
	 * Returns the elements attribute names as a set
	 * 
	 * @return
	 */
	public Set<String> getAttributesNames() {
		return this.attributes.keySet();
	}
	
	/** 
	 * Returns element tag name . (Eg. "div")
	 * @return
	 */
	public String getTag() {
		return tag;
	}

	/**
	 * Sets element tag name .
	 * @param tag
	 */
	public void setTag(String tag) {
		this.tag = tag;
	}

	/**
	 * Returns the element containing text .
	 * @return
	 */
	public String getText() {
		return text;
	}

	/**
	 * Sets up the element containing text .
	 * @param text
	 */
	public void setText(String text) {
		this.text = text;
	}

	/** 
	 * Appends a certain element as a child . (Child is added last) .
	 * @param element
	 */
	public void addChildren(XMLElement element) {
		element.setParent(this);
		this.directChildren.add(element);
	}
	
	/**
	 * Inserts the specified element to idx position as a children .
	 * @param idx
	 * @param element
	 */
	public void addChildren(Integer idx, XMLElement element) {
		element.setParent(this);
		this.directChildren.add(idx, element);
	}
	
	public void addChildren(Iterable<XMLElement> children) {
		Iterator<XMLElement> it = children.iterator();
		while(it.hasNext()) {
			addChildren(it.next());
		}
	}
	
	/**
	 * Returns an iterator over XML Element children .
	 * @return
	 */
	public Iterator<XMLElement> getDirectChildren() {
		return directChildren.iterator();
	}

	/**
	 * Returns direct children of the element based on a filter .
	 * @param filter The filter .
	 * @return
	 */
	public Iterator<XMLElement> getDirectChildren(XMLElementFilter filter) {
		List<XMLElement> result = new LinkedList<XMLElement>();
		for (XMLElement element : directChildren) {
			if (filter.condition(element)) {
				result.add(element);
			}
		}
		return result.iterator();
	}

	/**
	 * Get all children of the element .
	 * @return
	 */
	public Iterator<XMLElement> getAllChildren() {
		XMLElement tmp = null;
		Stack<XMLElement> stack = null;
		Iterator<XMLElement> it = null;
		List<XMLElement> result = new LinkedList<XMLElement>();
		for (XMLElement element : this.directChildren) {
			XMLElement current = null;
			result.add(element);
			stack = new Stack<XMLElement>();
			stack.push(element);
			while (!stack.isEmpty()) {
				current = stack.pop();
				it = current.getDirectChildren();
				while(it.hasNext()) {
					tmp = it.next();
					stack.push(tmp);
					result.add(tmp);
				}
			}
		}
		return result.iterator();
	}

	/**
	 * Get all children of a particular element based on a filter .
	 * @param filter
	 * @return
	 */
	public Iterator<XMLElement> getAllChildren(XMLElementFilter filter) {
		XMLElement tmp = null;
		Iterator<XMLElement> allChildren = getAllChildren();
		List<XMLElement> result = new LinkedList<XMLElement>();
		while(allChildren.hasNext()) {
			tmp = allChildren.next();
			if (filter.condition(tmp)) {
				result.add(tmp);
			}
		}
		return result.iterator();
	}
	
	public XMLElement getParent() {
		return parent;
	}

	protected void setParent(XMLElement parent) {
		this.parent = parent;
	}
	
	/** 
	 * Returns the XML representation of this particular element .
	 * @return
	 */
	public String toXML() {
		return "";
	}

	public String toJSON() {
		return "";
	}

	public void writeToXML(OutputStream outputStream) {
	}

	public void writeToXML(File file) {
	}

	public void writeToJSON(OutputStream outputStream) {
	}
	
	public void writeToJSON(File file) {
	}
}
